###############################################################
=begin
Nombre del archivo: RubyServer.rb
Universidad del Valle de Guatemala
Curso: Sistemas Operativos Avanzados
Autor: Martín Guzmán - 08041
Fecha: Septiembre de 2010
Descripción: Servidor Web hecho para el proyecto #1
   - Este proyecto consta de varios módulos.
	- Demonio - correr desde que empieza
	- Módulo de conexión - manejar múltiples conexiones 
	  simultáneas
	- Módulo de interpretación - petición GET
	- Módulo de mapeo - "/usr/www/data"+PETICIÓN
	- Módulo de autorización - Basic Authentication - archivos htmls
	- Módulo de auditoría y registro - log de acceso
	- Módulo de respuesta - forma la petición y revisa caché
		- 5 archivos en caché
=end
###############################################################

require 'socket'
require 'date'
require 'logger'
require 'base64'
require 'rexml/document'
require '/root/Server/src/Cache'
require '/root/Server/src/Answer'
include REXML

comienzo = "/root/Server/"

begin
	# Código que puede dar problemas
	doc = Document.new File.new("#{comienzo}config.xml")
	root_file = doc.root
	log_start = root_file.attributes['log_dir'].to_s
	$port = root_file.attributes['puerto'].to_i
	$max_cache = root_file.elements["cache"].attributes["cache_max_len"].to_i
	$root = root_file.elements["rootDir"].attributes["root_dir"].to_s

rescue => detail

	log_start = "./"
	$port = 8080
	$max_cache = 5
	$root = "./"

ensure

	#Add "/" if missing
	if ($root[$root.length-1] != 47)
	  $root << "/"
	end
	
	#Add "/" if missing
	if (log_start[log_start.length-1] != 47)
	  log_start << "/"
	end
	
	$log = Logger.new("#{log_start}access_#{Date.today.day}-#{Date.today.month}-#{Date.today.year}.log")
	$log.level = Logger::DEBUG	
end


begin
	$passwords = File.open("#{comienzo}passwords.txt", 'r').read().split("\n")
rescue => detail
	puts "NO SE PUDO CARGAR EL ARCHIVO DE PASSWORDS"
	$passwords = Array.new
	$passwords << "admin:admin"
end



###############################################################
# Clase Conection
# representa una conexión
###############################################################
class Conection
	def initialize(client)
		@client = client
		@client_IP = Socket.unpack_sockaddr_in(client.getpeername)[1] #[0]-> es el port
		@answer = Answer.new
		@url = ""
		@get_from = ""
		@request = client.readline #request
		@authorization = ""
		@has_auto = false
		@authorization_txt = "* Autorización: No trae credenciales"
		
		while((a = client.readline).length>2)
			if(a=~/Authorization: Basic .*\n/)
				puts "ya detecte el Authrzation"
				@authorization = a.split(" ")[2]
				@has_auto = true
				@authorization_txt = "* Autorización: Sí trae credenciales - #{@authorization} - #{Base64::decode64(@authorization)}"
			end
		end

	end
	
	#------------------------------------------------------
	# map() -> mapea la url hacia un archivo existente
	#------------------------------------------------------
	def map(url)
		return ($root+"index.html") if(url=="")
		return ($root + url)
	end

	#------------------------------------------------------
	# analizeRequest() -> analiza la solicitud y dependiendo
	# de ella almacena en @answer la respuesta
	#------------------------------------------------------
	def analyzeRequest()
		puts "Request entrante: #{@request}"#TODO quitar este debug..
		case @request
		when /GET .* HTTP\/1\.1/

			@url = map(@request[5..-12])
			fileType = @url.split('.')[@url.split('.').length-1]
			
			#puts "@url = #{@url} - fileType .#{fileType}."
			
			if(File.exist?(@url)) #verificar que exista
			
				
				if(fileType == "htmls")
				
					#si existe y no necesita credenciales
					
					correct = false
					if(@has_auto)
					
						#puts "."+@authorization+"."
						c = Base64::decode64(@authorization)
						if($passwords.include?(c))
							correct = true
						end
					end
					
					if(correct)
						@authorization_txt << "\n\tCredencial aceptada"
						@get_from = $cache.addToCache(@url)
						@answer.content = $cache.getContent(@url)
						@answer.setHTTP_code("ok",fileType)
					else
						@authorization_txt << "\n\tCredencial no aceptada"
						@get_from = "programa"
						@answer.setHTTP_code("Authorization Required",fileType)
					end
				
				#revisar si es carpeta
				elsif(fileType =~/.*\//)
					#TODO ver qué devolver si es carpeta => en Answer/setHTTP_code/when "folder found"
					@get_from = "programa"
					@answer.setHTTP_code("Folder Found",fileType)
					
				else
					
					
					#si existe y no necesita credenciales
					@authorization_txt << "\n\tLa petición no requería credenciales"
					@get_from = $cache.addToCache(@url)
					@answer.content = $cache.getContent(@url)
					@answer.setHTTP_code("OK",fileType)
					puts ""+("-"*50+"\n")+"SI existe #{@url}"+"\n"+("-"*50+"\n")#TODO quitar este debug..
					
					
				end
	

			else
				
				#no existe el archivo
				@get_from = "programa"
				@answer.setHTTP_code("Not Found",fileType)
				puts ""+("-"*50+"\n")+"NO existe #{@url}"+"\n"+("-"*50+"\n")#TODO quitar este debug..


			end

		else
			
			#no es una request de GET válido
			@get_from = "programa"
			@answer.setHTTP_code("Bad Request","")
			
		end
	end

	#------------------------------------------------------
	# sendAnswer() -> envía la respuesta al cliente
	#------------------------------------------------------
	def sendAnswer()
		saveAccessLog(@client_IP, @url, @answer.HTTP_code, @answer.content.length, @get_from,@request.chomp!, @authorization_txt)
		@client.puts @answer.answer()
	end	
	
	#------------------------------------------------------
	# close() -> cierra la conexión con el cliente
	#------------------------------------------------------
	def close()
		@client.close
	end

end


###############################################################
# print_logo
# sirve para imprimir el logo del inicio
###############################################################
def print_logo()
	temp = "\n            _             _     ___                                      " << "\n"
	temp << "   _ __    (_)   _ _     (_)   / __|   ___     _ _  __ __   ___     _ _  " << "\n"
	temp << "  | '  \\   | |  | ' \\    | |   \\__ \\  / -_)   | '_| \\ V /  / -_)   | '_| " << "\n"
	temp << "  |_|_|_| _|_|_ |_||_|  _|_|_  |___/  \\___|  _|_|_  _\\_/_  \\___|  _|_|_  " << "\n"
	temp << "__|\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"||\"\"\"\"\"|__" << "\n"
	temp << "  `-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'`-0-0-'" << "\n"
	temp << ""
	puts temp#TODO quitar este debug..
	return temp
end

###############################################################
# saveAccessLog
# sirve para guardar el log de una conexión con algún cliente
###############################################################
def saveAccessLog(client_IP, url, http_code, bytes_len, from, request, authorization_txt)
	temp = ("#"*60)+"\n"
	temp << ("* Solicitud hecha: #{request}") << "\n"
	temp << ("* Hora y fecha de acceso: "+Time.now.asctime) << "\n"
	temp << ("* IP del cliente: #{client_IP}") << "\n"
	temp << ("* URL solicitada: #{url}") << "\n"
	temp << ("#{authorization_txt}") << "\n"
	temp << ("* Código HTTP resultado:\n\n#{http_code}\n") << "\n"
	temp << ("* Bytes transferidos: #{bytes_len}") << "\n"
	temp << ("* Leido desde: #{from}")
	temp << "\n"
	temp << ("#"*60)+"\n"
	
	$log.info "\n"+temp+"\n"
	
	puts temp	#TODO quitar este debug..
end

###############################################################
# ----------------- inicio de la ejecución --------------------
###############################################################
$cache = Cache.new($max_cache)

$log.info "\n"+print_logo()+"\n"

temp = "Configuración del servidor:"+"\n"
temp << "\t"+"* Puerto a escuchar: #{$port}"+"\n"
temp << "\t"+"* Tamaño de caché: #{$max_cache}"+"\n"
temp << "\t"+"* Directorio base: #{$root}"+"\n"

$log.info "\n"+temp+"\n"

$server = TCPServer.new($port)


loop do
	Thread.start($server.accept) do |client|
		#client = $server.accept #TODO comentar esto si se pone el Thread
		conection = Conection.new(client)
		
		conection.analyzeRequest()
		conection.sendAnswer()
		
		conection.close()
	end
end
